package com.ysw.spring.spring_util.template;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.function.Supplier;

/**
 * redis分布式锁工具，redis 锁的实现建议使用redisson的锁实现
 *
 * @author zenghw
 */
//@Component
@Slf4j
public final class RedisLockUtil {

    @Autowired(required = false)
    @Qualifier("lockStringRedisTemplate")
    private StringRedisTemplate lockStringRedisTemplate;


    /**
     * 获取是否被上锁
     *
     * @param key 锁的key
     * @return
     */
    public Boolean isLock(String key) {
        return lockStringRedisTemplate.hasKey(key);
    }

    /**
     * 获取锁后执行，该方法会一直等待，等待获取到锁为止
     *
     * @param key      锁的key
     * @param expire   超时时间，单位为毫秒
     * @param supplier
     * @return
     */
    public <T> T executeWithLock(String key, long expire, Supplier<T> supplier) {
        this.lockUtilSuccess(key, expire);
        T result;
        try {
            result = supplier.get();
        } finally {
            this.unLock(key);
        }
        return result;
    }

    /**
     * 获取锁。如果锁被占有则阻塞，直到获取到锁。
     *
     * @param key    锁的key
     * @param expire 超时时间，单位为毫秒
     */
    public void lockUtilSuccess(String key, long expire) {
        while (true) {
            boolean hold = this.lock(key, expire);
            if (!hold) {
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("线程被interrupt了。", e);
                    throw new RuntimeException(e);
                }
            } else {
                break;
            }
        }
    }


    /**
     * 加锁，加锁成功并且方法执行完毕后使用{@linkplain #unLock(String)}释放锁
     *
     * @param key    redis key
     * @param expire 过期时间，单位为毫秒
     * @return true:加锁成功，false，加锁失败
     */
    public boolean lock(String key, long expire) {
        ValueOperations<String, String> opsForValue = lockStringRedisTemplate.opsForValue();
        long value = System.currentTimeMillis() + expire;
        Boolean status = opsForValue.setIfAbsent(key, String.valueOf(value));

        if (status) {
            return true;
        }

        long oldExpireTime = parseLongEx(opsForValue.get(key));
        // 这里判断就是判断是否锁过期了。
        if (oldExpireTime < System.currentTimeMillis()) {
            //锁续命
            long newExpireTime = System.currentTimeMillis() + expire;
            // 这里判断
            long currentExpireTime = parseLongEx(opsForValue.getAndSet(key, String.valueOf(newExpireTime)));
            if (currentExpireTime == oldExpireTime) {
                return true;
            }
        }
        return false;
    }

    /**
     * 释放锁
     *
     * @param key 锁的key
     */
    public void unLock(String key) {
        ValueOperations<String, String> opsForValue = lockStringRedisTemplate.opsForValue();
        long oldExpireTime = parseLongEx(opsForValue.get(key));
        if (oldExpireTime > System.currentTimeMillis()) {
            lockStringRedisTemplate.delete(key);
        }
    }

    private long parseLongEx(String raw) {
        if (StringUtils.isBlank(raw)) {
            return 0;
        }

        return Long.parseLong(raw);
    }

}